﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ESRI.ArcGIS.DataManagementTools;
using ESRI.ArcGIS.Geodatabase;
using ESRI.ArcGIS.Geometry;
using ESRI.ArcGIS.Geoprocessor;
using ESRI.ArcGIS.esriSystem;

namespace Bes.Swsp.ToolBox
{
  public class FeatureClassUtility
  {
    public static IFeatureClass CreateFeatureClass(IWorkspace workspace, string name, esriGeometryType geometryType, ISpatialReference spatialRef, string idFieldName, bool overwrite)
    {
      const string SHAPE_FIELD = "Shape";

      if (overwrite)
      {
        //Delete any existing
        IEnumDatasetName datasets = workspace.get_DatasetNames(esriDatasetType.esriDTFeatureClass);
        datasets.Reset();
        IDataset existing = null;
        IDatasetName datasetName = datasets.Next();
        while (datasetName != null)
        {
          if (string.Compare(name, datasetName.Name, true) == 0)
          {
            existing = (IDataset)((IName)datasetName).Open();
            break;
          }
          datasetName = datasets.Next();
        }
        if (existing != null)
        {
          try
          {
            existing.Delete();
          }
          finally
          {
            ComUtility.ReleaseComObject(existing);
          }
        }
      }

      IFeatureWorkspace featureWorkspace = workspace as IFeatureWorkspace;

      //Get elements to create a new table/feature class
      IFieldsEdit fields = new FieldsClass();

      IGeometryDefEdit geometryDef = new GeometryDefClass();
      geometryDef.GeometryType_2 = geometryType;
      geometryDef.SpatialReference_2 = spatialRef;

      IFieldEdit shapeField = new FieldClass();
      shapeField.Name_2 = SHAPE_FIELD;
      shapeField.Type_2 = esriFieldType.esriFieldTypeGeometry;
      shapeField.GeometryDef_2 = geometryDef;
      fields.AddField(shapeField);

      IFieldEdit idField = new FieldClass();
      idField.Name_2 = idFieldName;
      idField.Type_2 = esriFieldType.esriFieldTypeInteger;
      idField.Length_2 = 16;
      fields.AddField(idField as IField);

      //Create feature class
      return featureWorkspace.CreateFeatureClass(name, fields, null, null, esriFeatureType.esriFTSimple, SHAPE_FIELD, null);
    }

    public static int AddField(ref IFeatureClass featureClass, IField templateField, bool useGeoprocessor)
    {
      string validFieldName = FeatureClassUtility.ValidateField(featureClass, templateField.Name);
      return AddField(ref featureClass, validFieldName, templateField, useGeoprocessor);
    }

    public static int AddField(ref IFeatureClass featureClass, string fieldName, IField templateField, bool useGeoprocessor)
    {
      esriFieldType fieldType = templateField.Type == esriFieldType.esriFieldTypeOID ? esriFieldType.esriFieldTypeInteger : templateField.Type;
      return AddField(ref featureClass, fieldName, fieldType, templateField.Length, useGeoprocessor);
    }

    public static int AddField(ref IFeatureClass featureClass, string fieldName, esriFieldType fieldType, bool useGeoprocessor)
    {
      return AddField(ref featureClass, fieldName, fieldType, 50, useGeoprocessor);
    }

    public static int AddField(ref IFeatureClass featureClass, string fieldName, esriFieldType fieldType, int width, bool useGeoprocessor)
    {
      int fieldIndex = -1;
      if (useGeoprocessor)
        fieldIndex = _AddFieldGeoprocessor(ref featureClass, fieldName, fieldType, width);
      else
        fieldIndex = _AddField(featureClass, fieldName, fieldType, width);

      Logger.Log(string.Format("'{0}' field added to {1}", fieldName, GeoprocessingUtility.GetFeatureClassPath(featureClass)));

      return fieldIndex;
    }

    private static int _AddField(IFeatureClass featureClass, string fieldName, esriFieldType fieldType, int width)
    {
      IFieldEdit field = new FieldClass();
      field.Name_2 = fieldName;
      field.DefaultValue_2 = 0.0;
      field.Type_2 = fieldType; 
      if (fieldType == esriFieldType.esriFieldTypeString || fieldType == esriFieldType.esriFieldTypeBlob) 
        field.Length_2 = width; 
      featureClass.AddField(field); 
      return featureClass.FindField(fieldName);
    }

    private static int _AddFieldGeoprocessor(ref IFeatureClass featureClass, string fieldName, esriFieldType fieldType, int width)
    {
      IName featureClassName = ((IDataset)featureClass).FullName;

      Geoprocessor gp = GeoprocessingUtility.GetGeoprocessor(true, false, true);
      try
      {
        AddField addFieldTool = new AddField();
        addFieldTool.in_table = featureClass;
        addFieldTool.field_name = fieldName;
        addFieldTool.field_type = _GetFieldTypeText(fieldType);
        if (fieldType == esriFieldType.esriFieldTypeString || fieldType == esriFieldType.esriFieldTypeBlob)
          addFieldTool.field_length = width;
        object result = GeoprocessingUtility.RunGpTool(gp, addFieldTool, false);
      }
      finally
      {
        GeoprocessingUtility.ResetGeoprocessor();
      }

      // Reopen to force refresh of feature class object to reflect changes
      featureClass = (IFeatureClass)featureClassName.Open();
      return featureClass.FindField(fieldName);
    }

    private static string _GetFieldTypeText(esriFieldType fieldType)
    {
      switch (fieldType)
      {
        case esriFieldType.esriFieldTypeBlob:
          return "BLOB";
        case esriFieldType.esriFieldTypeDate:
          return "DATE";
        case esriFieldType.esriFieldTypeDouble:
          return "DOUBLE";
        case esriFieldType.esriFieldTypeGlobalID:
        case esriFieldType.esriFieldTypeGUID:
          return "GUID";
        case esriFieldType.esriFieldTypeInteger:
          return "LONG";
        case esriFieldType.esriFieldTypeRaster:
          return "RASTER";
        case esriFieldType.esriFieldTypeSingle:
          return "FLOAT";
        case esriFieldType.esriFieldTypeSmallInteger:
          return "SHORT";
        case esriFieldType.esriFieldTypeString:
          return "TEXT";
        default:
          throw new Exception(string.Format("Cannot add field of type {0}.", fieldType));
      }
    }

    public static string ValidateField(IFeatureClass featureClass, string fieldName)
    {
      string validFieldName = fieldName;
      Geoprocessor gp = GeoprocessingUtility.GetGeoprocessor(true, false, true);
      try
      {
        validFieldName = gp.ValidateFieldName(fieldName, ((IDataset)featureClass).Workspace.PathName);
      }
      finally
      {
        GeoprocessingUtility.ResetGeoprocessor();
      }
      return validFieldName;
    }

    public static string GetTempFeatureClassName(IWorkspace workspace, string baseName)
    {
      //NOTE: Even if workspace is case sensitive, this code will work: The returned name is guaranteed to be unique.
      HashSet<string> existingNames = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);

      IEnumDatasetName datasets = workspace.get_DatasetNames(esriDatasetType.esriDTFeatureClass);
      datasets.Reset();
      IDatasetName dataset = datasets.Next();
      while (dataset != null)
      {
        if (!existingNames.Contains(dataset.Name))
          existingNames.Add(dataset.Name);
        dataset = datasets.Next();
      }

      int suffix = 1;
      string name = baseName;
      while (existingNames.Contains(name))
      {
        name = string.Format("{0}{1}", baseName, suffix++);
      }

      return name;
    }
  }
}
